#!/usr/bin/env python
# encoding: utf-8
# Author: 潘世友
# Email: stpann@163.com

__note__: str = '''
本程序用于计算监管要求下的信用风险加权资产（RWA）和资本要求（K），根据2023年11月1日国家金融监督管理总局令第4号公布的 \
《商业银行资本管理办法》“附件6 信用风险内部评级法风险加权资产计量规则”，针对商业银行采用内部评级法的，应当按照以下规则 \
计量主权、金融机构、公司和零售风险暴露的信用风险加权资产。具体包括：
（一）计算信用风险暴露的相关性（R）
1.主权风险暴露、专业贷款、一般公司风险暴露.
2.金融机构风险暴露
3.中小企业风险暴露
4.零售风险暴露
4.1.个人住房抵押贷款
4.2.合格循环零售风险暴露
4.3.其他零售风险暴露
（二） 计算期限调整因子（b）
（三）计算信用风险暴露的资本要求（K）
1.非零售风险暴露
2.零售风险暴露
（四）计算信用风险暴露的风险加权资产（RWA）
'''

from math import exp, log, sqrt
from typing import Literal

from scipy.stats import norm

from rmwheels.utils.check import check_para

EXPOSURE: dict = {
    'cor': '主权、一般公司风险暴露',
    'fin': '金融机构风险暴露',
    'sme': '中小企业风险暴露',
    'mor': '个人住房抵押贷款',
    'rec': '合格循环零售贷款',
    'rtl': '其他零售风险暴露'
}


@check_para({'pdv': (0, 1),
        'exposure': list(EXPOSURE.keys())})
def rwa_r(pdv: float,
          exposure: Literal['cor', 'fin', 'sme', 'mor', 'rec', 'rtl'] = 'cor',
          s: float | int = 3) -> float:
    """
    根据输入的债项敞口类型及pd值，计算该债项的监管相关性（R）

    Parameters
    ----------
    pdv: float，债项的违约概率
    exposure: Literal['cor', 'fin', 'sme', 'mor', 'rec', 'rtl'] = 'cor'，债项的敞口类型，默认值为cor
        - 'cor': 主权、一般公司风险暴露
        - 'fin': 金融机构风险暴露
        - 'sme': 中小企业风险暴露
        - 'mor': 个人住房抵押贷款
        - 'rec': 合格循环零售贷款
        - 'rtl': 零售风险暴露
    s: float | int = 3，当敞口类型为sme中小企业风险暴露时，应输入该中小企业的年营业收入，单位为千万

    Returns
    -------
    r：float，根据相关性公式返回该债项的监管相关性结果

    Examples
    --------
    >>> rwa_r(0.05, 'cor')
    0.12985019983486787
    >>> rwa_r(0.05, 'fin')
    0.16231274979358484
    >>> rwa_r(0.05, 'sme', 10)
    0.10022057020523824
    >>> rwa_r(0.05,'mor')
    0.15
    >>> rwa_r(0.05,'rec')
    0.04
    >>> rwa_r(0.05, 'rtl')
    0.052590612648557786
    """
    if exposure == 'sme' and s is None:
        raise ValueError("当债项的敞口类型为sme时，必须提供年营业收入参数(s)")

    if exposure == 'mor':
        return 0.15
    if exposure == 'rec':
        return 0.04

    base_r = 0.12 * (1 - 1 / exp(50 * pdv)) / (1 - 1 / exp(50)) + 0.24 * (
            1 - (1 - 1 / exp(50 * pdv)) / (1 - 1 / exp(50)))

    if exposure == 'cor':
        return base_r
    if exposure == 'fin':
        return 1.25 * base_r
    if exposure == 'sme':
        return base_r - 0.04 * (1 - (s - 3) / 27)
    if exposure == 'rtl':
        return 0.03 * (1 - 1 / exp(35 * pdv)) / (1 - 1 / exp(35)) + 0.16 * (
                1 - (1 - 1 / exp(35 * pdv)) / (1 - 1 / exp(35)))


@check_para({'pdv': (0, 1),
        'lgd': (0, 1),
        'm': (0, None),
        'exposure': list(EXPOSURE.keys())})
def rwa_k(pdv: float,
          lgd: float = 0.45,
          m: float | int = 1,
          exposure: Literal['cor', 'fin', 'sme', 'mor', 'rec', 'rtl'] = 'cor',
          s: float | int = 3) -> float:
    """
    根据输入的债项信息输出该债项的监管资本K要求

    Parameters
    ----------
    pdv: float，债项的违约概率
    lgd: float = 0.45，债项的违约损失概率
    m:float | int=1，债项的期限，单位为年
    exposure: Literal['cor', 'fin', 'sme', 'mor', 'rec', 'rtl'] ='cor'，债项的敞口类型，默认值为cor
        - 'cor': 主权、一般公司风险暴露
        - 'fin': 金融机构风险暴露
        - 'sme': 中小企业风险暴露
        - 'mor': 个人住房抵押贷款
        - 'rec': 合格循环零售贷款
        - 'rtl': 其他零售风险暴露
    s: float | int =3，当敞口类型为sme中小企业风险暴露时，应输入该中小企业的年营业收入，单位为千万

    Returns
    -------
    k：float，根据资本要求公式返回该债项的监管资本K要求结果

    Examples
    --------
    >>> rwa_k(0.05, 0.45, 1, 'cor')
    0.1055195186790008
    >>> rwa_k(0.05, 0.45, 1, 'fin')
    0.12649400188243085
    >>> rwa_k(0.05, 0.45, 1,'sme', 10)
    0.0860044396003196
    >>> rwa_k(0.05, 0.45, 1,'mor')
    0.11857765857155467
    >>> rwa_k(0.05, 0.45, 1,'rec')
    0.043795689869319705
    >>> rwa_k(0.05, 0.45, 1, 'rtl')
    0.05313213475109771
    """
    r = rwa_r(pdv, exposure, s)
    m = max(1, m)
    k = (norm.cdf(sqrt(1 / (1 - r)) * norm.ppf(pdv) + sqrt(r / (1 - r)) * norm.ppf(0.999)) - pdv) * lgd
    k = float(k)
    if exposure in ['cor', 'fin', 'sme']:
        b = (0.11852 - 0.05478 * log(pdv)) ** 2
        return k * ((1 + (m - 2.5) * b) / (1 - 1.5 * b))
    else:
        return k


if __name__ == '__main__':
    print(rwa_r(0.05, 'cor'))
    print(rwa_r(0.05, 'fin'))
